#include "PortManager.h"
#include <stdexcept>
#include <iostream>
#include "utils.h"
#include "deplibUV.h"

static inline void onCloseUdp(uv_handle_t* handle) {
	delete reinterpret_cast<uv_udp_t*>(handle);
}

uv_handle_t* PortManager::Bind(Protocol protocol, std::string& ip, uint16_t port, SocketFlags& flags) {
	
    Utils::IP::NormalizeIp(ip);

	int err;
	const int family = Utils::IP::GetFamily(ip);
	struct sockaddr_storage bindAddr{};
		
    uv_handle_t* uvHandle { nullptr };
	std::string protocolStr;
	const uint8_t bitFlags = ConvertSocketFlags(flags, protocol, family);

	switch (protocol){
		case Protocol::UDP:{
			protocolStr.assign("udp");
			break;
		}
        default:
            return nullptr; // Unsupported protocol.
	}

	switch (family) {
		case AF_INET: {
			err = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in*>(&bindAddr));
			if (err != 0) {
				std::runtime_error("uv_ip4_addr() failed: " + std::string(uv_strerror(err)));
			}
			break;
		}
		case AF_INET6: {
			err = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in6*>(&bindAddr));

			if (err != 0) {
                std::runtime_error("uv_ip6_addr() failed: " + std::string(uv_strerror(err)));
			}
			break;
		}

		// This cannot happen.
		default:
            std::runtime_error("unknown IP family");
	}
	// Set the port into the sockaddr struct.
	switch(family)
	{
		case AF_INET: {
			(reinterpret_cast<struct sockaddr_in*>(&bindAddr))->sin_port = htons(port);
			break;
		}
		case AF_INET6: {
			(reinterpret_cast<struct sockaddr_in6*>(&bindAddr))->sin6_port = htons(port);
			break;
		}
	}

	// Try to bind on it.
	switch (protocol) {
		case Protocol::UDP: {
			uvHandle = reinterpret_cast<uv_handle_t*>(new uv_udp_t());
			err      = uv_udp_init_ex(
          	DepLibUV::GetLoop(), reinterpret_cast<uv_udp_t*>(uvHandle), UV_UDP_RECVMMSG);
			break;
		}
        default:
            return nullptr;
	}

	if (err != 0) {
		switch (protocol) {
			case Protocol::UDP: {
				delete reinterpret_cast<uv_udp_t*>(uvHandle);
				throw std::runtime_error(
				  "uv_udp_init_ex() failed [protocol:" + protocolStr + ", ip:'" + ip + "', port:" + std::to_string(port) + "]: " + uv_strerror(err));
				break;
			}
		}
	}

	switch (protocol) {
		case Protocol::UDP: {
			err = uv_udp_bind(
				  reinterpret_cast<uv_udp_t*>(uvHandle),
				  reinterpret_cast<const struct sockaddr*>(&bindAddr),
				  bitFlags);

			if (err != 0) {
					// If it failed, close the handle and check the reason.
				uv_close(reinterpret_cast<uv_handle_t*>(uvHandle), static_cast<uv_close_cb>(onCloseUdp));					
				throw std::runtime_error(
					  "uv_udp_bind() failed [protocol:" + protocolStr + ", ip:'" + ip + "', port:" + std::to_string(port) + "]: " + uv_strerror(err));
			}
			break;
		}
	}
	std::cout << "PortManager::Bind() succeeded [protocol:" << protocolStr
		          << ", ip:'" << ip << "', port:" << port << "]" << std::endl;
	return static_cast<uv_handle_t*>(uvHandle);
}


uint8_t PortManager::ConvertSocketFlags(SocketFlags& flags, Protocol protocol, int family) {

	uint8_t bitFlags{ 0b00000000 };

		// Ignore ipv6Only in IPv4, otherwise libuv will throw.
	if (flags.ipv6Only && family == AF_INET6) {
		switch (protocol) {
			case Protocol::UDP: {
				bitFlags |= UV_UDP_IPV6ONLY;
				break;
			}

			case Protocol::TCP: {
				bitFlags |= UV_TCP_IPV6ONLY;
				break;
			}
		}
	}

	// Ignore udpReusePort in TCP, otherwise libuv will throw.
	if (flags.udpReusePort && protocol == Protocol::UDP) {
		bitFlags |= UV_UDP_REUSEADDR;
	}

	return bitFlags;
}

void PortManager::Unbind(uint64_t hash, uint16_t port) {
	// This function is a placeholder for unbinding logic.
	// The actual implementation would depend on how the port management is structured.
	// For now, we will just print a message indicating unbinding.
	std::cout << "Unbinding port: " << port << " with hash: " << hash << std::endl;
}